package com.siashan.toolkit.crypt.symmetric;

import com.siashan.toolkit.crypt.CryptException;
import com.siashan.toolkit.crypt.DecoderException;
import com.siashan.toolkit.crypt.binary.Base64;
import com.siashan.toolkit.crypt.binary.Hex;
import com.siashan.toolkit.crypt.util.StringUtils;

import java.io.Serializable;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

/**
 * RC4加密解密算法实现<br>
 * 来自：https://github.com/xSAVIKx/RC4-cipher/blob/master/src/main/java/com/github/xsavikx/rc4/RC4.java
 *
 * @author siashan
 * @since 1.0.7
 */
public class RC4 implements Serializable {
    private static final long serialVersionUID = 1L;

    private static final int SBOX_LENGTH = 256;
    /** 密钥最小长度 */
    private static final int KEY_MIN_LENGTH = 5;

    /**
     * 加密
     *
     * @param message 消息
     * @param charset 编码
     * @return 密文
     */
    public static byte[] encrypt(String message, Charset charset,String key)  {
        return crypt(StringUtils.getBytes(message, charset),getKey(key));
    }

    /**
     * 加密，使用默认编码：UTF-8
     *
     * @param message 消息
     * @param key     秘钥
     * @return 密文
     */
    public static byte[] encrypt(String message, String key)  {
        return encrypt(message, StandardCharsets.UTF_8,key);
    }

    /**
     * 加密，使用默认编码：UTF-8
     *
     * @param message 消息
     * @param key     秘钥
     * @return 密文
     */
    public static String encryptBase64(String message, String key)  {
        return Base64.encodeBase64String(encrypt(message,key));
    }

	/**
	 * 加密，使用默认编码：UTF-8
	 *
	 * @param message 消息
	 * @param key     秘钥
	 * @return 密文
	 */
	public static String encryptHex(String message, String key)  {
		return Hex.encodeHexString(encrypt(message,key));
	}

	/**
	 * 加密
	 *
	 * @param data 数据
	 * @return 加密后的Hex
	 * @since 4.5.12
	 */
	public static byte[] encrypt(byte[] data,String key) {
		return crypt(data,getKey(key));
	}


	/**
     * 加密
     *
     * @param data 数据
     * @return 加密后的Hex
     * @since 4.5.12
     */
    public static String encryptHex(byte[] data,String key) {
        return Hex.encodeHexString(encrypt(data,key));
    }

    /**
     * 加密
     *
     * @param data 数据
     * @return 加密后的Base64
     * @since 4.5.12
     */
    public static String encryptBase64(byte[] data,String key) {
        return Base64.encodeBase64String(encrypt(data,key));
    }



    /**
     * 解密
     *
     * @param message 消息
     * @param charset 编码
     * @param key     秘钥
     * @return 明文
     * @throws CryptException key长度小于5或者大于255抛出此异常
     */
    public static String decrypt(byte[] message, Charset charset,String key) throws CryptException {
        return StringUtils.newString(crypt(message,getKey(key)), charset);
    }

    /**
     * 解密，使用默认编码UTF-8
     *
     * @param message 消息
     * @param key     秘钥
     * @return 明文
     */
    public static String decrypt(byte[] message,String key)  {
        return decrypt(message, StandardCharsets.UTF_8,key);
    }

    /**
     * 解密Hex（16进制）表示的字符串，使用默认编码UTF-8
     *
     * @param message 消息
     * @param key     秘钥
     * @return 明文
     */
    public static String decryptHex(String message,String key) throws DecoderException {
        return decrypt(Hex.decodeHex(message),StandardCharsets.UTF_8,key);
    }

    /**
     * 解密Hex（16进制）或Base64表示的字符串
     *
     * @param message    明文
     * @param charset 解密后的charset
     * @param key      秘钥
     * @return 明文
     */
    public static String decryptHex(String message, Charset charset,String key) throws DecoderException {
        return decrypt(Hex.decodeHex(message),charset,key);
    }


    /**
     * 加密或解密指定值，调用此方法前需初始化密钥
     *
     * @param msg 要加密或解密的消息
     * @param sbox sbox
	 * @return 加密或解密后的值
     */
    public static byte[] crypt(final byte[] msg,int[] sbox) {
        byte[] code;
        code = new byte[msg.length];
        int i = 0;
        int j = 0;
        for (int n = 0; n < msg.length; n++) {
            i = (i + 1) % SBOX_LENGTH;
            j = (j + sbox[i]) % SBOX_LENGTH;
            swap(i, j, sbox);
            int rand = sbox[(sbox[i] + sbox[j]) % SBOX_LENGTH];
            code[n] = (byte) (rand ^ msg[n]);
        }
        return code;
    }

    /**
     * 设置密钥
     *
     * @param key 密钥
     * @throws CryptException key长度小于5或者大于255抛出此异常
     */
    public static int[] getKey(String key) throws CryptException {
        final int length = key.length();
        if (length < KEY_MIN_LENGTH || length >= SBOX_LENGTH) {
            throw new CryptException("Key length has to be between " + KEY_MIN_LENGTH + " and " + (SBOX_LENGTH - 1));
        }
        return initSBox(StringUtils.getBytesUtf8(key));
    }

    //----------------------------------------------------------------------------------------------------------------------- Private method start

    /**
     * 初始化Sbox
     *
     * @param key 密钥
     * @return sbox
     */
    private static int[] initSBox(byte[] key) {
        int[] sbox = new int[SBOX_LENGTH];
        int j = 0;

        for (int i = 0; i < SBOX_LENGTH; i++) {
            sbox[i] = i;
        }

        for (int i = 0; i < SBOX_LENGTH; i++) {
            j = (j + sbox[i] + (key[i % key.length]) & 0xFF) % SBOX_LENGTH;
            swap(i, j, sbox);
        }
        return sbox;
    }

    /**
     * 交换指定两个位置的值
     *
     * @param i 位置1
     * @param j 位置2
     * @param sbox 数组
     */
    private static void swap(int i, int j, int[] sbox) {
        int temp = sbox[i];
        sbox[i] = sbox[j];
        sbox[j] = temp;
    }
    //----------------------------------------------------------------------------------------------------------------------- Private method end


    public static void main(String[] args) throws DecoderException {
        String rawStr = "i want to test RC4 crypt";
        String key = "sk8w9rfsjdhg9834wyrwfhw938r89w";
        String  encrypt = RC4.encryptHex(rawStr, key);
        System.out.println("加密后：" + encrypt);

        String decryptHex = RC4.decryptHex(encrypt, key);
        System.out.println(decryptHex);
    }
}
